package com.fenyin.samples.study.jdk.nio.charset;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.UnsupportedCharsetException;

/**
 * Test charset decoding.
 * 
 * @author Ron Hitchens (ron@ronsoft.com)
 */
public class CharsetDecode {
	/**
	 * Test charset decoding in the general case, detecting and handling buffer
	 * under/overflow and flushing the decoder state at end of input. This code
	 * reads from stdin and decodes the ASCII-encoded byte stream to chars. The
	 * decoded chars are written to stdout. This is effectively a 'cat' for
	 * input ascii files, but another charset encoding could be used by simply
	 * specifying it on the command line.
	 */
	public static void main(String[] argv) throws IOException {
		// Default charset is standard ASCII
		String charsetName = "ISO-8859-1";
		// Charset name can be specified on the command line
		if (argv.length > 0) {
			charsetName = argv[0];
		}
		// Wrap a Channel around stdin, wrap a channel around stdout,
		// find the named Charset and pass them to the decode method.
		// If the named charset is not valid, an exception of type
		// UnsupportedCharsetException will be thrown.
		decodeChannel(Channels.newChannel(System.in), new OutputStreamWriter(System.out), Charset.forName(charsetName));
	}

	/**
	 * General purpose static method which reads bytes from a Channel, decodes
	 * them according
	 * 
	 * @param source
	 *            A ReadableByteChannel object which will be read to EOF as a
	 *            source of encoded bytes.
	 * @param writer
	 *            A Writer object to which decoded chars will be written.
	 * @param charset
	 *            A Charset object, whose CharsetDecoder will be used to do the
	 *            character set decoding. Java NIO 206
	 */
	public static void decodeChannel(ReadableByteChannel source, Writer writer,
			Charset charset) throws UnsupportedCharsetException, IOException {
		// Get a decoder instance from the Charset
		CharsetDecoder decoder = charset.newDecoder();
		// Tell decoder to replace bad chars with default mark
		decoder.onMalformedInput(CodingErrorAction.REPLACE);
		decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
		// Allocate radically different input and output buffer sizes
		// for testing purposes
		ByteBuffer bb = ByteBuffer.allocateDirect(16 * 1024);
		CharBuffer cb = CharBuffer.allocate(57);
		// Buffer starts empty; indicate input is needed
		CoderResult result = CoderResult.UNDERFLOW;
		boolean eof = false;
		while (!eof) {
			// Input buffer underflow; decoder wants more input
			if (result == CoderResult.UNDERFLOW) {
				// decoder consumed all input, prepare to refill
				bb.clear();
				// Fill the input buffer; watch for EOF
				eof = (source.read(bb) == -1);
				// Prepare the buffer for reading by decoder
				bb.flip();
			}
			// Decode input bytes to output chars; pass EOF flag
			result = decoder.decode(bb, cb, eof);
			// If output buffer is full, drain output
			if (result == CoderResult.OVERFLOW) {
				drainCharBuf(cb, writer);
			}
		}
		// Flush any remaining state from the decoder, being careful
		// to detect output buffer overflow(s)
		while (decoder.flush(cb) == CoderResult.OVERFLOW) {
			drainCharBuf(cb, writer);
		}
		// Drain any chars remaining in the output buffer
		drainCharBuf(cb, writer);
		// Close the channel; push out any buffered data to stdout
		source.close();
		writer.flush();
	}

	/**
	 * Helper method to drain the char buffer and write its content to the given
	 * Writer object. Upon return, the buffer is empty and ready to be refilled.
	 * 
	 * @param cb
	 *            A CharBuffer containing chars to be written.
	 * @param writer
	 *            A Writer object to consume the chars in cb.
	 */
	private static void drainCharBuf(CharBuffer cb, Writer writer) throws IOException {
		cb.flip(); // Prepare buffer for draining
		// This writes the chars contained in the CharBuffer but
		// doesn't actually modify the state of the buffer.
		// If the char buffer was being drained by calls to get( ),
		// a loop might be needed here.
		if (cb.hasRemaining()) {
			writer.write(cb.toString());
		}
		cb.clear(); // Prepare buffer to be filled again
	}
}
